//
//  ZYImageCache.m
//  ZYWebImage
//
//  Created by wangzhipan on 2025/3/17.
//

#import "ZYImageCache.h"
#import "ZYDiskCache.h"
#import "ZYMemoryCache.h"
#import "ZYDiskConfig.h"
#import "UIImage+Cost.h"
#import "UIImage+Data.h"
#import "UIImage+Extra.h"
#import "ZYImageDecoderFactory.h"
#import "ZYWebImageConst.h"
#import "ZYImageCacheTask.h"

#define kDefaultCacheDirectoryPath [[ZYImageCache userCacheDirectory] stringByAppendingPathComponent:@"com.ZYWebImage.www"]
@interface ZYImageCache()
@property (nonatomic, strong, nullable) ZYDiskCache *diskCache;
@property (nonatomic, strong, nullable) ZYMemoryCache *memoryCache;
@property (nonatomic, strong, nullable) dispatch_queue_t ioQueue;
@end

@implementation ZYImageCache
+ (instancetype)sharedImageCache
{
    static id _instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}
+ (NSString *)userCacheDirectory
{
    return NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
}

- (instancetype)init
{
    if (self = [super init]) {
        ZYDiskConfig *defaultConfig = [[ZYDiskConfig alloc] init];
        self.diskCache = [[ZYDiskCache alloc] initWithCachePath:kDefaultCacheDirectoryPath
                                                         config:defaultConfig];
        
        //默认100Mb 内存缓存，最多缓存2000条图片
        self.memoryCache = [[ZYMemoryCache alloc] initWithMaxCost:100 * 1024 * 1024 maxCountLimit:2000];
        
        self.ioQueue = dispatch_queue_create("com.ZYWebImage.www", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}


//sync
- (void)storeImageToMemory:(UIImage *)image
                    forKey:(NSString *)key
{
    if (!image || !key) {
        return;
    }
    NSUInteger cost = image.zy_cost;
    [self.memoryCache setObject:image forKey:key cost:cost];
}

//sync
- (void)storeImageToDisk:(UIImage *)image
                  forKey:(NSString *)key
                  format:(ZYImageFormat)format
{
    if (!image || !key) {
        return;
    }
    NSData *imageData = [image zy_dataOfFormat:format];
    //一些框架预设的图片额外属性信息，也存入磁盘中
    NSData *extraData = [self p_archivedExtraDataForImage:image];
    dispatch_sync(self.ioQueue, ^{
        [self.diskCache saveData:imageData forKey:key];
        [self.diskCache saveExtraData:extraData forKey:key];
    });
}

//sync
- (void)storeImageToAll:(UIImage *)image
                 forKey:(NSString *)key
                 format:(ZYImageFormat)format
{
    if (!image || !key) {
        return;
    }
    [self storeImageToMemory:image forKey:key];
    [self storeImageToDisk:image forKey:key format:format];
}

//sync
- (void)storeImageDataToDisk:(NSData *)imageData
                      forKey:(NSString *)key
{
    if (!imageData || !key) {
        return;
    }
    dispatch_sync(self.ioQueue, ^{
        [self.diskCache saveData:imageData forKey:key];
    });
}


//sync
- (nullable UIImage *)imageFromDiskCacheForKey:(NSString *)key
{
    if (!key) {
        return nil;
    }
    NSData *imageData = [self p_diskImageDataForKey:key];
    NSData *extraData = [self p_diskExtraDataForKey:key];
    NSDictionary *extraInfo = [self p_unarchivedExtraData:extraData];
    ZYImageFormat format = [extraInfo[kExtraFormat] intValue];
    UIImage *image = [self p_decodeImageData:imageData format:format];
    [image zy_configExtraInfo:extraInfo];
    return image;
}

//sync
- (nullable UIImage *)imageFromMemoryCacheForKey:(NSString *)key
{
    if (!key) {
        return nil;
    }
    return [self.memoryCache objectForKey:key];
}

//sync
- (nullable UIImage *)imageFromCacheForKey:(NSString *)key
{
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (!image) {
        image = [self imageFromDiskCacheForKey:key];
    }
    return image;
}

//sync
- (BOOL)diskImageDataExistsWithKey:(NSString *)key
{
    if (!key) {
        return NO;
    }
    __block BOOL isExist = NO;
    dispatch_sync(self.ioQueue, ^{
        isExist = [self.diskCache containDataForKey:key];
    });
    return isExist;
}

//sync
- (void)removeImageFromMemoryForKey:(NSString *)key
{
    [self.memoryCache removeObjectForKey:key];
}

//sync
- (void)removeImageFromDiskForKey:(NSString *)key
{
    if (!key) {
        return;
    }
    dispatch_sync(self.ioQueue, ^{
        [self.diskCache removeDataForKey:key];
    });
}

//sync
- (void)removeImageFromCacheForKey:(NSString *)key
{
    [self removeImageFromMemoryForKey:key];
    [self removeImageFromDiskForKey:key];
}

//sync
- (nullable NSString *)cachePathForKey:(NSString *)key
{
    if (!key) {
        return nil;
    }
    return [self.diskCache cachePathForKey:key];
}



//async
- (void)storeImage:(UIImage *)image
            forKey:(NSString *)key
              type:(ZYImageCacheType)type
            format:(ZYImageFormat)format
         completed:(nullable dispatch_block_t)completed
{
    if (!image || !key) {
        completed ? completed() : nil;
        return;
    }
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        ZYImageFormat zy_format = format;
        if (zy_format == ZYImageFormatUnknow) {
            zy_format = image.zy_format;
        }
        NSData *imageData = [image zy_dataOfFormat:zy_format];
        NSData *extraData = [self p_archivedExtraDataForImage:image];
        dispatch_async(self.ioQueue, ^{
            [self.diskCache saveData:imageData forKey:key];
            [self.diskCache saveExtraData:extraData forKey:key];
            if (completed) {
                SafeAsyncMainThread(^{
                    completed();
                });
            }
        });
    });
}

//async
- (void)storeImageData:(NSData *)imageData
                forKey:(NSString *)key
             completed:(nullable dispatch_block_t)completed
{
    dispatch_async(self.ioQueue, ^{
        [self.diskCache saveData:imageData forKey:key];
        if (completed) {
            SafeAsyncMainThread(^{
                completed();
            });
        }
    });
}

//async
- (ZYImageCacheTask *)queryCacheTaskForKey:(NSString *)key
                                      type:(ZYImageCacheType)type
                                 completed:(nullable ZYImageCacheQueryCompletionBlock)completed
{
    if (!key || type == ZYImageCacheTypeNone) {
        completed ? completed(nil, nil, ZYImageCacheTypeNone) : nil;
        return nil;
    }
    
    UIImage *image = [self.memoryCache objectForKey:key];
    if (image) {
        completed ? completed(image, nil, ZYImageCacheTypeMemory) : nil;
        return nil;
    }
    ZYImageCacheTask *task = [[ZYImageCacheTask alloc] initWithKey:key completed:completed];
 
    NSData *(^queryDiskDataBlock)(void) = ^NSData * {
        @synchronized (task) {
            if (task.isCancelled) {
                return nil;
            }
        }
        NSData *data = [self.diskCache dataForKey:key];
        return data;
    };
    
    UIImage *(^queryImageBlock)(NSData *data) = ^UIImage *(NSData *diskData) {
        @synchronized (task) {
            if (task.isCancelled) {
                return nil;
            }
        }
        //先从内存取
        UIImage *diskImage = [self.memoryCache objectForKey:key];
        
        //内存没有，再从磁盘里面取
        if (!diskImage && diskData) {
            NSData *extraData = [self p_diskExtraDataForKey:key];
            NSDictionary *extraInfo = [self p_unarchivedExtraData:extraData];
            ZYImageFormat format = [extraInfo[kExtraFormat] intValue];
            diskImage = [self p_decodeImageData:diskData format:format];
            if (diskImage) {
                diskImage.zy_format = format;
                //磁盘取完，同步存在内存中
                [self storeImageToMemory:diskImage forKey:key];
            }
        }
        return diskImage;
    };
    
    dispatch_async(self.ioQueue, ^{
        NSData *diskData = queryDiskDataBlock();
        UIImage *diskImage = queryImageBlock(diskData);
        @synchronized (task) {
            if (task.isCancelled) {
                return;
            }
        }
        SafeAsyncMainThread(^{
            @synchronized (task) {
                if (task.isCancelled) {
                    return;
                }
            }
            completed ? completed(diskImage, diskData, ZYImageCacheTypeDisk) : nil;
        });
    });
    return task;
}
//async
- (void)removeImageForKey:(NSString *)key
                     type:(ZYImageCacheType)type
                completed:(nullable dispatch_block_t)completed
{
    if (!key) {
        return;
    }
    if (type == ZYImageCacheTypeMemory || type == ZYImageCacheTypeAll) {
        [self.memoryCache removeObjectForKey:key];
        
    }

    if (type == ZYImageCacheTypeDisk || type == ZYImageCacheTypeAll) {
        dispatch_async(self.ioQueue, ^{
            [self.diskCache removeObjectForKey:key];
            SafeAsyncMainThread(^{
                completed ? completed() : nil;
            });
        });
    }
    else {
        completed ? completed() : nil;
    }
}

/**
 * Synchronously Clear all memory cached images
 */
- (void)clearMemory
{
    [self.memoryCache removeAllObjects];
}

/**
 * Asynchronously clear all disk cached images. Non-blocking method - returns immediately.
 * @param completion    A block that should be executed after cache expiration completes (optional)
 */
- (void)clearDiskOnCompletion:(nullable dispatch_block_t)completion
{
    dispatch_async(self.ioQueue, ^{
        [self.diskCache removeAllData];
        if (completion) {
            SafeAsyncMainThread(^{
                completion();
            });
        }
    });
}

/**
 * Asynchronously remove all expired cached image from disk. Non-blocking method - returns immediately.
 * @param completion A block that should be executed after cache expiration completes (optional)
 */
- (void)deleteOldFilesWithCompletionBlock:(nullable dispatch_block_t)completion
{
    dispatch_async(self.ioQueue, ^{
        [self.diskCache removeExpiredData];
        if (completion) {
            SafeAsyncMainThread(^{
                completion();
            });
        }
    });
}

/**
 * Get the total bytes size of images in the disk cache
 */
- (NSUInteger)totalDiskSize
{
    __block NSUInteger totalSize = 0;
    dispatch_sync(self.ioQueue, ^{
        totalSize = [self.diskCache totalSize];
    });
    return totalSize;
}

/**
 * Get the number of images in the disk cache
 */
- (NSUInteger)totalDiskCount
{
    __block NSUInteger totalCount = 0;
    dispatch_sync(self.ioQueue, ^{
        totalCount = [self.diskCache totalCount];
    });
    return totalCount;
}


- (nullable NSData *)p_diskImageDataForKey:(NSString *)key
{
    if (!key) {
        return nil;
    }
    __block NSData *imageData = nil;
    dispatch_sync(self.ioQueue, ^{
        imageData = [self.diskCache dataForKey:key];
    });
    return imageData;
}

- (nullable NSData *)p_diskExtraDataForKey:(NSString *)key
{
    if (!key) {
        return nil;
    }
    __block NSData *extraData = nil;
    dispatch_sync(self.ioQueue, ^{
        extraData = [self.diskCache extraDataForKey:key];
    });
    return extraData;
}

- (nullable UIImage *)p_decodeImageData:(NSData *)imageData format:(ZYImageFormat)format
{
    id<IZYDecoder>decoder = [ZYImageDecoderFactory createDecoderForData:imageData
                                                                 format:format];
    return [decoder decodeImage];
}

- (nullable NSData *)p_archivedExtraDataForImage:(UIImage *)image
{
    if (!image) {
        return nil;
    }
    NSDictionary *extarInfo = image.zy_allExtraInfo;
    return [NSKeyedArchiver archivedDataWithRootObject:extarInfo
                                 requiringSecureCoding:YES
                                                 error:nil];
}

- (NSDictionary *)p_unarchivedExtraData:(NSData *)extraData
{
    if (!extraData) {
        return nil;
    }
    NSDictionary *extraInfo = [NSKeyedUnarchiver
                               unarchivedObjectOfClass:[NSDictionary class]
                               fromData:extraData
                               error:nil];
    return extraInfo;
}
@end
